#ifndef CSERVER_REST_API_H_
#define CSERVER_REST_API_H_
#include <memory>
#include <unordered_map>
#include <functional>
#include <unordered_set>
#include <chrono>
#include <vector>
#include <mutex>
#include <set>

#include <fcgiapp.h>
#include <fcgio.h>
#include <pqxx/connection.hxx>
#include <pqxx/transaction.hxx>

namespace csrv{

	struct Request;
	std::map<std::string, std::string> parseQuery(const std::string& query);
	std::map<std::string, std::string> parseCookie(const std::string& cookie);
	std::string makeCookie(const std::map<std::string, std::string>& cookie);
	void response_header(std::ostream& os, int code, bool endhead = true);
	void response_error(std::ostream& os, int code, const std::string& cat, const std::string& reason, Request& req);
	void response_error(std::ostream& os, int code, const std::string& cat, const std::string& reason, const std::string& callback);
	extern std::string escape_buffer(const std::vector<char>& buf);
	extern std::string escape_char(char b);
	extern std::string escape_string(const std::string& str);
	std::string escape_null(const std::string& s);
	extern std::string hash_password(const std::string& account, const std::string& password);
	extern std::string result2json(const pqxx::result& result, size_t strColidx);

	struct TotalCount{
		TotalCount(pqxx::work& conn, const std::string& sql);
		std::string result();

		template<typename T> TotalCount& operator()(T&& arg){
			invo(std::forward<T>(arg));
			return *this;
		}

		private:
		pqxx::work& w;
		pqxx::prepare::invocation invo;
	};


	struct UserInfo{
		std::string displayName, password, userid, role;
		std::chrono::system_clock::time_point last_access;
	};

	struct SessionInfo{
		std::string userid, account, displayName ;
		std::string orgid, orgname;
		std::unordered_map<std::string, std::string> module_role;
		std::chrono::system_clock::time_point last_access;
	};

	struct SessionContext{
		std::mutex& mutex;
		SessionContext(std::mutex& m) : mutex(m){}
		std::unordered_map<std::string, UserInfo> user_info;			// account ==> user_info
		std::unordered_map<std::string, std::unordered_set<std::string>> role_permission;			// role ==> permited api
		std::unordered_map<std::string, std::string> activeSessions;	// session_id ==> account

		std::unordered_map<std::string, SessionInfo> sessionTable;	// for api/v2, session_id --> SessionInfo;
	};

	class Context;
	class RestApi{
	public:
		RestApi(Context& );
		~RestApi();
		void start();
		void stop();
		struct RestApiImp;
	private:
		std::unique_ptr<RestApiImp> m_imp;
	};

	using RestApiPtr = std::shared_ptr<RestApi>;

	struct Request{
		explicit Request(int sock_id){
			FCGX_InitRequest(&req, sock_id, 0);
		}
		~Request(){ FCGX_Finish_r(&req);}
		bool accept(){
			if (FCGX_Accept_r(&req) < 0)
				return false;
			auto cookie_str = FCGX_GetParam("HTTP_COOKIE", req.envp);
			if (cookie_str)
				cookies = parseCookie(cookie_str);
			return true;
		}

		Context* context;
		FCGX_Request req;

		Request(const Request&) = delete;
		Request& operator=(const Request&) = delete;

		std::string getParam(const std::string& key){
			auto s = FCGX_GetParam(key.c_str(), req.envp);
			return s ? s : std::string();
		}

		std::string getCookie(const std::string& key){
			auto pos = cookies.find(key);
			return pos == cookies.end()? std::string() : pos->second;
		}
		private:

		std::map<std::string, std::string> cookies;
	};

	struct RequestContext{
		std::reference_wrapper<Request> req;
		std::reference_wrapper<pqxx::connection> conn;
		std::reference_wrapper<SessionContext> sctx;
		std::string command;
		std::string method;
	};
	struct StreamGroup{
		std::vector<char> inbuf;
		fcgi_streambuf bin,bout;
		std::istream fin;
		std::ostream fout;

		explicit StreamGroup(Request& request, size_t bufferSize = 0)
			: inbuf(bufferSize)
			, bin(request.req.in, bufferSize >0 ? &inbuf[0]: nullptr, bufferSize)
			  , bout(request.req.out)
			  , fin(&bin)
			  , fout(&bout)
		{}
	};
	using CmdHandler = std::function<void(Request&, pqxx::connection&, SessionContext& sctx, const std::string&)>;

	struct CommandAddHelper{
		CommandAddHelper(std::string m, std::string c, CmdHandler f, std::set<std::string> roles = {"admin", "user", "visitor"});
	};

}
#endif

